Repository navigation
fix(metadata-protocol): a package publish judges each draft with the batch's own extension drafts folded onto their targets - #33300
Conversation
…batch's own extension drafts folded onto their targets collectBatchPendingDeclarations now collects the batch's object_extension drafts, and the runtime authoring gate folds their fields onto their targets in the object universe, so a formula or hop into a field a sibling extension draft adds resolves as it will once the batch has published. Claude-Session: https://claude.ai/code/session_016WV4cykhsWAtSL6Sr4VWFG Co-authored-by: Claude <noreply@anthropic.com>
…ing extension draft's field Claude-Session: https://claude.ai/code/session_016WV4cykhsWAtSL6Sr4VWFG Co-authored-by: Claude <noreply@anthropic.com>
# Conflicts: # packages/runtime/src/domains/meta-object-extension-item.test.ts
📓 Docs Drift CheckThis PR changes 1 package(s): 11 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 4 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6f64f5cc499914a75466e628592178db76d70cf7 && git checkout 6f64f5cc499914a75466e628592178db76d70cf7
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 24f2f4014d0c9bbd43ec6928942fa44b21c1dff6 475c84b5230bc68c48a4368be0661e8cb3bbc6a8 && git checkout -B drift-repro 24f2f4014d0c9bbd43ec6928942fa44b21c1dff6 && git merge --no-ff 475c84b5230bc68c48a4368be0661e8cb3bbc6a8
node scripts/docs-audit/affected-docs.mjs --json 24f2f4014d0c9bbd43ec6928942fa44b21c1dff6
|
|
domain:server#5 · os-elon-musk · session_016WV4cykhsWAtSL6Sr4VWFG Request: review · area A5 · head 475c84b · 2026-11-14T17:00Z Fixes #33152 · Clause-②: no · Metadata-protocol: none (open question for review: none vs added, see below). CI green on this head (35 success, 16 skipped, 0 failed); mergeable clean. Since the first head: one conflict-free-in-code merge of origin/main; the only conflict was Open question from the implementer: the batch door now accepts packages it refused before (sibling extension drafts). Declared |
|
Contract-review-claim: session_016WV4czuuneAppDnJbrNejw · head 475c84b · 2026-11-14T17:02Z review#2 (server area pilot, A5). |
Review: PASSHead: Checked:
On the declaration question: Nit (pre-existing, not introduced): the fold is additive, so a batch whose extension draft drops a field the published extension added still has that field in the served object while judging siblings. It was accepted before this change as well. |
|
domain:server#5 · os-elon-musk · session_016WV4cykhsWAtSL6Sr4VWFG ACCEPT · area A5 · head 475c84b · 2026-11-14T17:04Z
|
Requested by Elon · project thread
Fixes #33152
Clause-②: no — removes a false refusal the published contract already denies: the
@objectstack/metadata-protocolchangelog entry 0c24898 states "a package publishes as a self-consistent unit —publishPackageDraftsjudges each draft against the batch's own pending declarations", and the batch'sobject_extensiondrafts were the pending declarations left out. No exported type or signature changes (dist/index.d.tscarries nopendingmember at all; the changed type is module-internal).Metadata-protocol: none — no key, value or metadata type changes;
os buildalready accepts these packages, and the batch door now gives the same verdict.维护者速读
publishPackageDrafts,以及草稿读取时给出的“发布预判”)收集待发布集合时,现在也收集同包的对象扩展草稿,并在判定时把这些草稿的字段并到被扩展对象上。unknown field,整批回滚;os build却接受。作者只能按顺序逐个发布。packages/metadata-protocol/src/runtime-authoring-gate.ts(foldPendingObjectExtensions及其在evaluateRuntimeAuthoringGate中的两处调用)、packages/metadata-protocol/src/protocol-publish-batch.ts(收集器多了objectExtensions)、packages/runtime/src/domains/meta-object-extension-item.test.ts(新的 publishPackageDrafts judges a package's drafts without its own extension drafts, so a formula or hop into a field a sibling extension draft adds is refused and the whole batch rolls back #33152 用例)。pnpm --filter @objectstack/runtime exec vitest run src/domains/meta-object-extension-item.test.ts -t 33152,预期 3 个用例全部通过(两个“一次批量发布成功”,一个“引用不存在字段仍被拒、整批保持草稿”)。What changes
collectBatchPendingDeclarations(protocol-publish-batch.ts) collects the batch'sobject_extensiondrafts into a newobjectExtensionsentry of the pending set. The routing tableCLOSURE_CONTEXT_KEY_BY_TYPEgains the rowobject_extension: 'objectExtensions'; itssatisfiesclause now namesRuntimePendingDeclarations(the context collections plus this one key), so the drift guard and the completeness assertion overRuntimeStackContextare unchanged.RuntimePendingDeclarations(module-internal) isRuntimeStackContextplus an optionalobjectExtensions. It is not a context collection: the liveobjectsthe gate reads are already served (the registry folds every published extension onto its target), so a pending extension is folded the same way rather than joining a collection of its own.foldPendingObjectExtensions(runtime-authoring-gate.ts) adds each pending extension'sfieldsto its target in the object universe, merged by name with a later writer replacing the whole field: the registry'smergeObjectDefinitionsfield merge, and the one part of itobjectsAsServedfolds for the build. It runs aftermergePendingDeclarations, so a target that is itself a draft of the batch is folded too. On anobject_extensionwrite the written extension's own draft is left out (withoutTarget): the rules fold the written entry themselves, onto the target without the written package's extension (lint: the object_extension publish door runs no field-formula judgment, so a computed field os build refuses still publishes through PUT /meta/object_extension #33072).draftPublishVerdict) uses the same collector, so it gives the same answer.objectExtensionWriteFoldinprotocol-authoring-gate.ts) now describe the fix.Tests
packages/runtime/src/domains/meta-object-extension-item.test.ts, new top-level describe[#33152]on the file's real rig (better-sqlite3, the real ObjectQL engine and protocol). The base gainscrm_regionandcrm_site(a lookupregionto the region); the pair publishes in one batch:{ extend: 'crm_region', fields: { tier__c } }and{ extend: 'crm_site', fields: { tier_label__c: formula "record.region.tier__c + '!'" } }. Expectedoutcome: 'published', both rows active, both fields served.crm_custom_dealwhose formula hopsrecord.region.tier__c. Expectedoutcome: 'published'.tir__cinstead. Expectedoutcome: 'refused', the site row'scodeINVALID_METADATAnamingtier__c, both rows still drafts, and the region not servingtir__c.packages/metadata-protocol/src/protocol-publish-drafts-closure.test.ts(the publishPackageDrafts validation closure omits same-batch dataset drafts: a package shipping dashboard+dataset together can never publish (widget-dataset-unknown) #10377 closure harness), new top-level describe: the fold keeps the target's fields and adds the draft's, does not mutate the live entry, folds onto nothing when the target is in no universe, honourswithoutTarget; andevaluateRuntimeAuthoringGaterefuses an object's hop into the region'stier__c(codeINVALID_METADATA,status422) without the sibling extension draft and accepts it with it.Acceptance notes
mergePendingDeclarationsis. For a target the package already extends in a published extension, the draft's fields are folded over the served target, which still carries the published extension. A sibling's reference to a field the draft drops therefore still resolves (it did before this PR too). The single-item extension door narrows that case (lint: the object_extension publish door runs no field-formula judgment, so a computed field os build refuses still publishes through PUT /meta/object_extension #33072); the batch does not yet. Not measured; noted.@objectstack/lint(validate-expressions.ts, theruntimeWriteTypedocblock;authoring-rules.ts, the expression rule'ssurfacescomment) and the last bullet of the unreleased.changeset/33072-object-extension-door-formula.mdstill say the batch refuses this case. They are outside this claim's file surface and left for the seat.record.region__c.tier__c,region__cdeclared in the written extension,tier__cexisting nowhere) was published by the batch. That is a different path (a hop through an extension-added lookup); reported to the seat as a finding.Verification
Reverse verification, read on the final source: the three
metadata-protocolsource files restored to BASEd4fbb4dbb(origin/main), package rebuilt, andablation-dist-preflightconfirming the marker absent fromdist/. Both LIT cases red, the CONTROL green. Restore leg:git checkout HEAD --, every path's hash equal to its HEAD blob, rebuild, preflight confirms the marker indist/.Reverse verification (premise on origin/main code)
Local runs, all at head
6d0278e0d(after merging origin/main once). The incoming main commit touched onlyservice-automationand connector files, outside this diff's packages.Tests and typecheck (head 6d0278e)
Derived gate families (dispatch-gates --commands, 69)
Not run locally, declared to CI:
test:repo, the import-side packages' suites (the public surface is byte-identical, so none is owed), and the dogfood battery (no!in the title, no narrowing arm).Generated by Claude Code